package logique.solveur;

import ilog.concert.IloException;
import ilog.concert.IloLinearNumExpr;
import ilog.concert.IloNumVar;
import ilog.concert.IloNumVarType;
import ilog.cplex.IloCplex;
import ilog.cplex.IloCplex.UnknownObjectException;

import java.util.Hashtable;
import java.util.Vector;

import logique.outils.Chrono;
import logique.programme.Contrainte;
import logique.programme.FonctionEconomique;
import logique.programme.ProgrammeLineaire;
import logique.programme.Terme;
import logique.programme.Vecteur;
import logique.programme.FonctionEconomique.SensOptimisation;
import logique.solution.Affectation;
import logique.solution.Solution;
import logique.solution.Variable;
import logique.solution.VariableEntiere;
import logique.solution.VariableReelle;

public class Cplex implements Solveur, SolveurOptimisation, SolveurInitial {
	
	// Attributes //
	private static Cplex singleton;
	private Chrono chrono;
	public static long dureeOptimisation = 0;
	private IloCplex cplex;
	// Hashtable<X3, identifiant variable Cplex>
	private Hashtable<String, IloNumVar> variablesCplex;
	private Hashtable<String, Variable> variablesProgramme;
	private ProgrammeLineaire pl;
	
	// Methods //
	private Cplex() {
		super();
		
		// Création du chrono qui va calculer la durée d'optimisation
		chrono = new Chrono();
		chrono.start();
	}

	public static Cplex getInstance() {
		if (singleton == null) 
			singleton = new Cplex();
		return singleton;
	}

	public void construireProg() {
		// On lit notre structure de donn�es pour en construire une adapt�e � CPLEX
		try {
			// Cet objet va résoudre le probleme
			cplex = new IloCplex();
			
			// On lit les vecteurs qui interviennent dans le probleme
			lectureVariablesProgramme();
			
			// On lit toutes les variables intervenant dans le programme
			lectureVariablesCplex();
			
			// Ajout des contraintes du programme
			addContraintes();
			
			// Ajout de la fonction objectif
			addObjectif();
			
		} catch (IloException e) {
			e.printStackTrace();
		}
	}

	private void lectureVariablesCplex() {
		
		try {
			variablesCplex = new Hashtable<String, IloNumVar>();
			for (String key : variablesProgramme.keySet()) {
				// On verifie la nature de la variable, int ou float
				if (variablesProgramme.get(key) instanceof VariableEntiere) {
					VariableEntiere variableEntiere = (VariableEntiere) variablesProgramme.get(key);
					variablesCplex.put(variablesProgramme.get(key).getNomVecteur() + String.valueOf(variablesProgramme.get(key).getIndex()), 
							cplex.intVar(0, variableEntiere.getBorneMaxEnt()));
					
				} else if (variablesProgramme.get(key) instanceof VariableReelle) {
					VariableReelle variableReelle = (VariableReelle) variablesProgramme.get(key);
					variablesCplex.put(variablesProgramme.get(key).getNomVecteur() + String.valueOf(variablesProgramme.get(key).getIndex()), 
							cplex.numVar(0, variableReelle.getBorneMaxReel(), IloNumVarType.Float));
				}
				
			}
		} catch (IloException e) {
			e.printStackTrace();
		}
	}
	
	private void lectureVariablesProgramme() {
		variablesProgramme = new Hashtable<String, Variable>();
		for (Vecteur vec : pl.getVecteurs()) {
			for (int i = 1; i <= vec.getTaille(); i++) {
				variablesProgramme.put(vec.getVar(i).getNomVecteur() + vec.getVar(i).getIndex(), vec.getVar(i));
			}
		}
	}
	
	public void addObjectif() {
		try {
			// Fonction eco
			IloLinearNumExpr fonctionEco = cplex.linearNumExpr();
			
			// On construit la fonction economique
			Vector<Terme> termesFonctionEco = pl.getFonctionEconomique().getListeTermes();
			for (Terme terme : termesFonctionEco) {
				fonctionEco.setConstant(pl.getFonctionEconomique().getTermeConstant());
				fonctionEco.addTerm(terme.getCoeff(), variablesCplex.get(terme.getVariable().getNomVecteur() + String.valueOf(terme.getVariable().getIndex())));
			}
			
			// On ajoute la fonction eco au probleme
			if (pl.getFonctionEconomique().getOptimisation().equals(SensOptimisation.MIN)) 
				cplex.addMinimize(fonctionEco);
			else cplex.addMaximize(fonctionEco);
		} catch (IloException e) {
			e.printStackTrace();
		}
		
	}
	
	public void addContraintes() {
		try {
			// On ajoute les contraintes
			Vector<Contrainte> contraintes = pl.getContraintes();
			for (Contrainte contrainte : contraintes) {
				// Ajout d'une contrainte
				IloLinearNumExpr cplexContr = cplex.linearNumExpr();
				
				Vector<Terme> termes = contrainte.getListeTermes();
				for (Terme terme : termes) {
					// Ajout d'un terme dans la contrainte
					System.out.println("terme:" + terme.getCoeff() + terme.getVariable().getNomVecteur() + terme.getVariable().getIndex());
					System.out.println();
					
					cplexContr.addTerm(terme.getCoeff(), variablesCplex.get(terme.getVariable().getNomVecteur() + String.valueOf(terme.getVariable().getIndex())));
				}
				
				// Ajout de l'opérateur et de la valeur constante
				if (contrainte.getOperateur().equals("<=")) {
					cplex.addLe(cplexContr, contrainte.getTermeConstant());
				} else if (contrainte.getOperateur().equals(">=")) {
					cplex.addGe(cplexContr, contrainte.getTermeConstant());
				} else if (contrainte.getOperateur().equals("==")) {
					cplex.addEq(cplexContr, contrainte.getTermeConstant());
				} else System.out.println("Impossible de reconnaitre l'op�rateur");
				
			}
		} catch (IloException e) {
			e.printStackTrace();
		}
	}

	public void addVariableEntiere(Variable v) {

	}

	public void addVariableReelle(Variable v) {

	}
	
	public void construireProgSansOpt() {
		// On doit trouver une solution admissible sans contrainte
		try{
			// Cet objet va résoudre le probleme
			cplex = new IloCplex();

			// On lit les vecteurs qui interviennent dans le probleme
			lectureVariablesProgramme();

			// Ajout des contraintes du programme
			addContraintes();

			// On lit toutes les variables intervenant dans le programme
			lectureVariablesCplex();

		} catch (IloException e) {
			e.printStackTrace();
		}
	}
	
	public Solution resoudreProgramme(ProgrammeLineaire p, boolean initial) {
		
		Solution solution = null;
		Vector<Affectation<?>> affectations = null;
		
		// On sauvegarde une ref sur le PL
		pl = p;
		
		// Construction du probleme
		if (initial) {
			construireProgSansOpt();
		} else {
			construireProg();
		}

		// Resolution du probleme
		try {
			// Démarrage du chrono effectif de la résolution
			chrono.setPause(false);
			if (cplex.solve()) {//si on a trouve une solution optimale
				
				// On arrete le chrono
				chrono.setPause(true);
				Cplex.dureeOptimisation += chrono.getTime();
				
				// On lit les variables résultat
				affectations = new Vector<Affectation<?>>();
				if (!initial) {
					System.out.println(" Valeur optimale = " + cplex.getObjValue());
				}
				
				int i = 0;
				for (String key : variablesCplex.keySet()) {
					System.out.println(" " + variablesProgramme.get(key).getNomVecteur()+variablesProgramme.get(key).getIndex() + ": " + key + " = " + cplex.getValue(variablesCplex.get(key)));
				
					// Ajout d'un affectation
					affectations.add(variablesProgramme.get(key).creerAffectation(cplex.getValue(variablesCplex.get(key))));
					i++;
				}
			}
		} catch (UnknownObjectException e) {
			e.printStackTrace();
		} catch (IloException e) {
			e.printStackTrace();
		}
		
		// Exploitation des resultats
		solution = new Solution(affectations);
		
		// Terminaison de la résolution
		cplex.end();
		
		// TEMP affichage du chrono
		//System.out.println("Chrono : " + Cplex.dureeOptimisation);
		
		return solution;
	}

	public static void main(String[] args) {
//		FonctionEconomique fe = new FonctionEconomique(SensOptimisation.MAX);
//
//		Vecteur v = new Vecteur("x", 3);
//		VariableEntiere v1 = new VariableEntiere(v, 1, 0, 40);
//		VariableEntiere v2 = new VariableEntiere(v, 2, 0, 100);
//		VariableReelle v3 = new VariableReelle(v, 3, 0, Double.MAX_VALUE);
//		
//		fe.addTerme(v1, 1.0f);
//		fe.addTerme(v2, 2.0f);
//		fe.addTerme(v3, 3.0f);
//		
//		ProgrammeLineaire pl = new ProgrammeLineaire(fe);
//		v.setVariable(v1, 1);
//		v.setVariable(v2, 2);
//		v.setVariable(v3, 3);
//		pl.addVecteur(v);
//		
//		Contrainte c1 = new Contrainte("<=", 20.0f);
//		Terme t1 = new Terme(-1.0f, v1);
//		c1.addTerme(t1);
//		Terme t2 = new Terme(1.0f, v2);
//		c1.addTerme(t2);
//		Terme t3 = new Terme(1.0f, v3);
//		c1.addTerme(t3);
//		pl.addContrainte(c1);
//		
//		Contrainte c2 = new Contrainte("<=", 30.0f);
//		Terme t4 = new Terme(1.0f, v1);
//		c2.addTerme(t4);
//		Terme t5 = new Terme(-3.0f, v2);
//		c2.addTerme(t5);
//		Terme t6 = new Terme(1.0f, v3);
//		c2.addTerme(t6);
//		pl.addContrainte(c2);
//		
		
		FonctionEconomique fe = new FonctionEconomique(SensOptimisation.MAX);

		Vecteur vect1 = new Vecteur("W", 2);
		VariableReelle v1 = new VariableReelle(vect1, 1, 0, Double.MAX_VALUE);
		
		//Vecteur vect2 = new Vecteur("L", 1);
		VariableReelle v2 = new VariableReelle(vect1, 2, 0, Double.MAX_VALUE);

		fe.addTerme(v1, 1.0f);

		ProgrammeLineaire pl = new ProgrammeLineaire(fe);
		vect1.setVariable(v1, 1);
		vect1.setVariable(v2, 2);
		//vect2.setVariable(v2, 1);
		pl.addVecteur(vect1);
		//pl.addVecteur(vect2);

		Contrainte c1 = new Contrainte("<=", 0.0f);
		Terme t1 = new Terme(1.0f, v1);
		c1.addTerme(t1);
		Terme t2 = new Terme(-2.0f, v2);
		c1.addTerme(t2);
		pl.addContrainte(c1);
		
		Contrainte c2 = new Contrainte("<=", 0.0f);
		Terme t4 = new Terme(1.0f, v1);
		c2.addTerme(t4);
		Terme t5 = new Terme(-3.0f, v2);
		c2.addTerme(t5);
		pl.addContrainte(c2);
				
		System.out.println(pl);
		
		Cplex.getInstance().resoudreProgramme(pl, false);
	}
}